﻿using System;
using System.Collections.Generic;
using System.IdentityModel.Claims;
using System.Linq;
using System.Text;
using System.Web;

namespace Altairis.IdentityToolkit.PersonalCard {
    /// <summary>
    /// Class representing the <see cref="IdentityLinkProvider" /> for information cards.
    /// </summary>
    public abstract class PersonalCardIdentityLinkProvider : IdentityLinkProvider {
        private const string CharMapSSID = "QL23456789ABCDEFGHJKMNPRSTUVWXYZ";

        /// <summary>
        /// When overriden in derived class, checks if given provider supports certain type of claims.
        /// </summary>
        /// <param name="claimSet">The claim set to be analyzed.</param>
        /// <returns><c>true</c> if provider supports given claim set; otherwise <c>false</c>.</returns>
        public override bool Supports(ClaimSet claimSet) {
            if (claimSet == null) return false;
            IEnumerable<Claim> claimList = claimSet.FindClaims(ClaimTypes.PPID, Rights.PossessProperty);
            return claimList != null && claimList.Count() != 0;
        }

        /// <summary>
        /// When overriden in derived class, resolves the link between given claim set and user name.
        /// </summary>
        /// <param name="claimSet">The claim set.</param>
        /// <returns>membership user name linked to given claim set. Returns <c>null</c> when no link was found.</returns>
        public override string ResolveLink(ClaimSet claimSet) {
            // Validate arguments
            if (claimSet == null) throw new ArgumentNullException("claims");

            // Call internal method
            return this.InternalResolveLink(GetPPID(claimSet));
        }

        /// <summary>
        /// When overriden in derived class, creates the link between given claim set and user name.
        /// </summary>
        /// <param name="claimSet">The claim set.</param>
        /// <param name="userName">Membership user name.</param>
        public override void CreateLink(ClaimSet claimSet, string userName) {
            // Validate arguments
            if (claimSet == null) throw new ArgumentNullException("claims");
            if (userName == null) throw new ArgumentNullException("userName");
            if (string.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty string.", "userName");

            // Call internal method
            this.InternalCreateLink(GetPPID(claimSet), userName);
        }

        /// <summary>
        /// Gets the private personal identifier from InfoCard claim set.
        /// </summary>
        /// <param name="claimSet">The claim set.</param>
        /// <returns>PPID.</returns>
        protected static string GetPPID(ClaimSet claimSet) {
            // Find PPID
            IEnumerable<Claim> claimList = claimSet.FindClaims(ClaimTypes.PPID, Rights.PossessProperty);
            if (claimList == null || claimList.Count() == 0) throw new NotSupportedException("Supplied ClaimSet does not contain expected claim '" + ClaimTypes.PPID + "'.");

            // Get it as string
            return claimList.First().Resource.ToString();

        }

        /// <summary>
        /// Computes the site-specific ID from private personal identifier.
        /// </summary>
        /// <param name="ppid">The PPID.</param>
        /// <returns>site-specific ID.</returns>
        public static string ComputeSSID(string ppid) {
            // Compute hash of PPID
            byte[] ppidHash;
            var algo = System.Security.Cryptography.SHA1.Create();
            ppidHash = algo.ComputeHash(Convert.FromBase64String(ppid));

            // Format PPID hash as SSID
            StringBuilder ssidBuilder = new StringBuilder();
            for (int i = 0; i < 10; i++) {
                if (i == 3 || i == 7) ssidBuilder.Append('-');
                ssidBuilder.Append(CharMapSSID[ppidHash[i] % CharMapSSID.Length]);
            }
            return ssidBuilder.ToString();
        }

        /// <summary>
        /// Internal method for resolving links between PPID and user names.
        /// </summary>
        /// <param name="ppid">The private personal identifier.</param>
        /// <returns>user name or <c>null</c> when no link was found.</returns>
        protected abstract string InternalResolveLink(string ppid);

        /// <summary>
        /// Internal method for creating links between PPID and user names.
        /// </summary>
        /// <param name="ppid">The PPID.</param>
        /// <param name="userName">The user name.</param>
        protected abstract void InternalCreateLink(string ppid, string userName);

        /// <summary>
        /// Removes the link between membership user and PPID.
        /// </summary>
        /// <param name="ppid">The PPID.</param>
        public abstract void RemoveLink(string ppid);

        /// <summary>
        /// Removes the link between membership user and PPID.
        /// </summary>
        /// <param name="ppid">The PPID.</param>
        /// <param name="userName">The user name.</param>
        public abstract void RemoveLink(string ppid, string userName);

        /// <summary>
        /// Gets the identity links for current user.
        /// </summary>
        /// <returns>
        /// List of <see cref="PersonalCardIdentityLink"/>s.
        /// </returns>
        public IEnumerable<PersonalCardIdentityLink> GetLinksForUser() {
            HttpContext ctx = System.Web.HttpContext.Current;
            if (ctx == null) throw new NotSupportedException("This overload is supported only in HTTP context.");
            if (!ctx.Request.IsAuthenticated) throw new NotSupportedException("This overload is supported only for authenticated users.");
            return GetLinksForUser(ctx.User.Identity.Name);
        }

        /// <summary>
        /// Gets the identity links for given user.
        /// </summary>
        /// <param name="userName">User name.</param>
        /// <returns>
        /// List of <see cref="PersonalCardIdentityLink"/>s.
        /// </returns>
        public abstract IEnumerable<PersonalCardIdentityLink> GetLinksForUser(string userName);

    }

    /// <summary>
    /// Class representing link between membership user and personal card PPIDs.
    /// </summary>
    public class PersonalCardIdentityLink {

        /// <summary>
        /// Gets or sets the private personal identifier.
        /// </summary>
        /// <value>The private personal identifier.</value>
        public string PPID { get; set; }

        /// <summary>
        /// Gets or sets the site-specific identifier.
        /// </summary>
        /// <value>The site-specific identifier.</value>
        public string SSID { get; set; }

        /// <summary>
        /// Gets or sets the user name.
        /// </summary>
        /// <value>The user name.</value>
        public string UserName { get; set; }

        /// <summary>
        /// Gets or sets the date when unique ID was assigned to given user.
        /// </summary>
        /// <value>The date when unique ID was assigned to given user.</value>
        public DateTime DateAssigned { get; set; }
    }

}